{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Example #1 - pricing a straddle"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using some more complex (but still simple) operations, we can approximate the price of an ATMF straddle."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$ STRADDLE_{ATMF} \\approx \\frac{2}{\\sqrt{2\\pi}} F \\times \\sigma \\sqrt(T) $$\n",
    "$$ \\sigma = implied volatility $$\n",
    "$$ T = time-to-maturity $$\n",
    "$$ F = forward of the underlier $$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's start with defining the straddle's implied volatility and time-to-maturity. Note, we will assume F is equal to 1 and the straddle price can be scaled accordingly. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vol = 0.2\n",
    "time = 1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "2. * ( (1 / ( 2 * 3.14 ) ** 0.5 ) * vol * time ** 0.5 )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is a lot to type again and again if you want to price several straddles, which is really annoying and error prone. Let's define a function for this so that we can use it over and over"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def straddlePricer( vol, time ):\n",
    "    return 2. * ( ( 1. / ( 2 * 3.14 ) ** 0.5 ) * vol * time ** 0.5 )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice this doesn't immediately return anything to the output area. Rest assured the function is defined and we can begin using it. Below, we can compare the function's output to the output of the cell above."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print( straddlePricer( 0.2, 1.0 ) )\n",
    "print( 2. * ( ( 1. / ( 2 * 3.14 ) ** 0.5 ) * vol * time ** 0.5 ) )\n",
    "print( straddlePricer( 0.2, 1.0 ) == ( 2. * ( ( 1. / ( 2 * 3.14 ) ** 0.5 ) * vol * time ** 0.5 ) ) )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Input order doesn't matter as long as we let the function know what we're using as inputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print( straddlePricer( time=1.0, vol=0.2 ) )\n",
    "print( straddlePricer( vol=0.2, time=1.0 ) )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is nice, but what if I want to default to certain inputs? By setting the initial inputs below we're implictly calling each of these arguments \"optional\". Initially, we'll make only `time` and optional arguement (input).  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def straddlePricer( vol, time=1.0 ):\n",
    "    return 2. * ( ( 1 / ( 2 * 3.14 ) ** 0.5 ) * vol * time ** 0.5 )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "straddlePricer( 0.2 )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we'll make both `vol` and `time` optional."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def straddlePricer( vol=0.2, time=1.0 ):\n",
    "    return 2. * ( ( 1 / ( 2 * 3.14 ) ** 0.5 ) * vol * time ** 0.5 )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In other words, we don't need to pass these arguments to call the function. It will use 0.2 for `vol` and 1.0 for `time` by default unless instructed otherwise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "straddlePricer()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "straddlePricer( 0.22 )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice, there's π in the denominator of the straddle price formula, but the value we used above (3.14) is an rough approximation. Is there a more precise value we could use? Yes, we can use a library called `numpy`. Let's import it first below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can access functions of numpy by entering `numpy.xxxxx`, where `xxxxx` is the function you would like to use. `numpy`'s implementation of `pi` is simply `numpy.pi`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "numpy.pi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Typing `numpy` over and over again can get pretty tedious. Let's make it easier for ourselves by abbreviating the name. Python convention for `numpy` abbreviation is `np`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import datetime as dt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.pi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`numpy` also has a handy square root function (`np.sqrt`)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.sqrt( 4 )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's incorporate `np.pi` and `np.sqrt` into our simple straddle pricer to make things a little more precise and easier to read."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def straddlePricer( vol=0.2, time=1.0 ):\n",
    "    return 2. * ( ( 1 / np.sqrt( 2 * np.pi ) ) * vol * np.sqrt( time ) )\n",
    "\n",
    "straddlePricer()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's see what the difference is between our original implementation and our new and improved implemenation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "straddlePricer() - ( 2. * ( ( 1 / ( 2 * 3.14 ) ** 0.5 ) * vol * time ** 0.5 ) )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this case, the difference in precision and readability isn't huge, but that difference can be valuable at times. In addition to the functionality above, `numpy` can do a lot of other things. For instance, we can generate some random numbers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.rand()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Is there a way to see what functions are available? Yes, just tab after `np.`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#np."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Alternatively, we can call `dir` on `np` to see what is included."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dir(np)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Continuing with the prior example of pricing our straddle, we can also price the straddle using the Monte Carlo method. We need to generate a normally distributed set of random numbers to simulate the asset's movement through time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def straddlePricerMC(vol=0.2, time=1.0, mcPaths=100):\n",
    "    dailyVol = vol / np.sqrt( 252. )\n",
    "    resultSum = 0\n",
    "    for p in range( mcPaths ):\n",
    "        resultSum += np.abs( np.prod( ( 1 + np.random.normal( 0, dailyVol, int( round( time * 252 ) ) ) ) ) - 1 )\n",
    "    return resultSum / mcPaths\n",
    "\n",
    "straddlePricerMC()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There's a lot of new things going on here. Let's unpack it one line at a time."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We know the variance scales linearly with time, so we can either\n",
    "\n",
    "1. divide the variance by time and take the square root to get a daily volatility, or\n",
    "2. take the square root of variance (volatility) and divide by the root of time\n",
    "    \n",
    "Generally, the latter is clearer and simpler to understand since we typically think in vol terms, but you are free to use whichever method you want."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Option #1 above\n",
    "np.sqrt( vol ** 2 / 252 )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Comparing the two methods\n",
    "vol = 0.2\n",
    "var = vol ** 2\n",
    "sqrtVarOverTime = np.sqrt( var / 252 )\n",
    "volOverSqrtTime = vol / np.sqrt( 252 )\n",
    "valuesEqual = np.isclose( sqrtVarOverTime, volOverSqrtTime )\n",
    "print( f'sqrtVarOverTime = {sqrtVarOverTime}\\nvolOverSqrtTime = {volOverSqrtTime}\\nAre they close? {valuesEqual}' )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next line isn't super exciting, but we set the default value of our cumulative sum to be 0. So we're just defining resultSum and setting it equal to 0. If we don't do this we'll get an error. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "resultSum = 0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next we have a loop. There are different types of loops we can use. Here we use a `for` loop, which says \"iterate over each element in `range(mcPaths)`\". But wait...what's `range(mcPaths)`? `range` is a native python function that will return an iterator over a list of ints starting at 0 and going to x-1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "range10 = range( 10 )\n",
    "lst = list( range10 )\n",
    "print( lst )\n",
    "print( len( lst ) )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In our case, we don't really want to do anything with `p`, so it is good practice to set it to `_`. We just want to iterate through the loop `mcPaths` times. In the default case, the function runs through the loop 100 times."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def straddlePricerMC( vol=0.2, time=1.0, mcPaths=100 ):\n",
    "    dailyVol = vol / np.sqrt( 252. )\n",
    "    resultSum = 0\n",
    "    for _ in range( mcPaths ):\n",
    "        resultSum += np.abs( np.prod( 1 + ( np.random.normal( 0, dailyVol, int( round( time * 252 ) ) ) ) ) - 1 )\n",
    "    return resultSum / mcPaths\n",
    "\n",
    "straddlePricerMC()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To unpack what the function does at each iteration of the loop, let's unpack this one step at a time. We start with the innermost function call and work backwards from there. Let's ask for help to see what the `np.random.normal` method actually does. Thankfully, there are two handy ways to see a function's documentation.\n",
    "\n",
    "1. help\n",
    "2. ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "help(np.random.normal)\n",
    "# np.random.normal?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ok, so we know from the help function that the `np.random.normal` method takes three optional inputs: mean, standard deviation, and size of the array to generate multiple random numbers. It defaults to a distribution with a mean of zero and a standard deviation of 1, returning only 1 random number."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.normal()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Below we're going to call this method with a mean of zero (no drift) and a standard deviation of our daily vol, so that we can generate multiple days of returns. Specifically, we ask to generate the number of days to maturity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "time = 1\n",
    "nDays = time * 252\n",
    "dailyVol = vol / np.sqrt( 252. )\n",
    "print( nDays )\n",
    "\n",
    "np.random.normal( 0, dailyVol, nDays )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, given we have an asset return timeseries, how much is a straddle worth? We're interested in the terminal value of the asset and because we assume the straddle is struck ATM, we can just take the absolute value of the asset's deviation from the initial value (in this case, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.seed( 42 ) # guarantee the same result from the two random series\n",
    "\n",
    "returns = np.random.normal( 0, dailyVol, time * 252 )\n",
    "priceAtMaturity = np.prod( 1 + returns )\n",
    "changeAtMaturity = priceAtMaturity - 1\n",
    "absChangeAtMaturity = np.abs( changeAtMaturity )\n",
    "print( absChangeAtMaturity )\n",
    "\n",
    "# all together in one line\n",
    "np.random.seed( 42 )\n",
    "print( np.abs( np.prod( 1 + ( np.random.normal( 0, dailyVol, time * 252 ) ) ) - 1 ) )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's take a closer look at what we did above. This time, we're going to utilize another two libraries called pandas and perspective to make our life a little easier."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "from perspective import psp\n",
    "\n",
    "simulatedAsset = pd.DataFrame( np.random.normal( 0, dailyVol, time * 252 ) + 1, columns=['return'] )\n",
    "simulatedAsset['price'] = ( 1 * simulatedAsset['return'] ).cumprod()\n",
    "psp( simulatedAsset )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `for` loop ultimately just does the above for `mcPaths` number of times, and we ultimately take the average of the paths to find the expected value of the straddle."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mcPaths = 100\n",
    "resultSum = 0.\n",
    "for _ in range(mcPaths):\n",
    "    resultSum += np.abs( np.prod( 1 + np.random.normal( 0., dailyVol, time * 252 ) ) - 1 )\n",
    "print( resultSum / mcPaths )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This price is pretty close to the price from our original pricer. More paths should help get us even closer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "straddlePricerMC(mcPaths=2000)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2000 paths is a lot, but it looks like we're still not converging to the original price. If we add more paths there is a tradeoff with compute time. Luckily, Jupyter has made it really easy to see how fast our function is."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%timeit straddlePricerMC(mcPaths=2000)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That's pretty fast. we can do a lot more paths."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"1 path: {straddlePricerMC(mcPaths=1)}\")\n",
    "print(f\"2000 path: {straddlePricerMC(mcPaths=2000)}\")\n",
    "print(f\"5000 path: {straddlePricerMC(mcPaths=5000)}\")\n",
    "print(f\"10000 path: {straddlePricerMC(mcPaths=10000)}\")\n",
    "print(f\"100000 path: {straddlePricerMC(mcPaths=100000)}\")\n",
    "print(f\"Closed form approximation: {straddlePricer()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Can we improve the above MC implementation? Of course! We can generate our random asset series in one go. Remember the `size` argument of the `np.random.normal` function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nDays = time * 252\n",
    "size = (nDays, 15)\n",
    "simulatedAsset = pd.DataFrame(np.random.normal(0, dailyVol, size))\n",
    "simulatedAsset = (1 + simulatedAsset).cumprod()\n",
    "\n",
    "simulatedAsset.tail()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cool!...Let's visualize by plotting it with matplotlib."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.style.use('fivethirtyeight')\n",
    "\n",
    "fig = plt.figure(figsize=(8,6))\n",
    "ax = plt.axes()\n",
    "_ = ax.plot(simulatedAsset)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So let's incorporate that into a `pandas` version of the MC pricer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def straddlePricerMCWithPD(vol=0.2, time=1, mcPaths=100000):\n",
    "    dailyVol = vol / ( 252 ** 0.5 )\n",
    "    randomPaths = pd.DataFrame( np.random.normal( 0, dailyVol, ( time*252, mcPaths ) ) )\n",
    "    price = ( ( 1 + randomPaths ).prod() - 1 ).abs().sum() / mcPaths\n",
    "    return price\n",
    "\n",
    "straddlePricerMCWithPD()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
